local s_character_less = [[<]];
local s_character_greater = [[>]];
local s_character_underline = [[_]];
local s_character_colon = [[:]];
local s_character_slash = [[/]];
local s_character_equal = [[=]];
local s_character_double_apostrophe = [["]];
local s_character_apostrophe = [[']];
local s_character_exclamation = [[!]];
local s_character_hyphen = [[-]];

local _G_error = _G.error;
local _G_pcall = _G.pcall;
local string_len = string.len;
local string_find = string.find;
local string_gsub = string.gsub;
local string_sub = string.sub;
local string_match = string.match;

local xml = {};
-- 移除两端空白
function xml.StringTrim(str)
    return string_gsub(str, "^%s*(.-)%s*$", "%1");
end

-- 跳过空格
function xml.SkipSpaceCharacter(xml_text)
    return string_gsub(xml_text, "^%s*", "");
end

-- 是否是叶子节点
function xml.IsLeafNode(tagname)
    return tagname == "style" or tagname == "script";
end

-- 解析注释 <!-- annotation content -->
function xml.ParseAnnotation(xml_text)
    if (string_len(xml_text) < 4 or string_find(xml_text, "<!--", 1, true) ~= 1) then
        return xml_text
    end

    xml_text = string_sub(xml_text, 5);

    local pos = string_find(xml_text, "-->", 1, true);
    if (pos == nil) then
        _G_error("invalid xml!!!")
    end

    return xml_text.substr(pos + 3);
end

-- 解析文本节点
function xml.ParseText(xml_text, xml_node)
    local less_pos = string_find(xml_text, "<", 1, true);
    if (less_pos == 1 or xml.StringTrim(xml_text) == "") then
        return xml_text
    end

    if (less_pos == nil) then
        _G_error("invalid xml!!!");
    end
    local text= string_sub(xml_text, 1, less_pos - 1);
    xml_node[#xml_node + 1] = xml.StringTrim(text);

    return string_sub(xml_text, less_pos);
end

-- 解析节点
function xml.ParseXmlNode(xml_text, xml_node)
    if (string_sub(xml_text, 1, 1) ~= s_character_less) then
        _G_error("invalid xml!!!")
    end
    local sub_xml_node = {};

    -- 跳过 <
    xml_text = string_sub(xml_text, 2);

    -- 解析标签名
    local tagname = string_match(xml_text, "^[^%s>]+");
    if (tagname == nil) then
        _G_error("invalid tagname!!!")
    end

    sub_xml_node.name = tagname;
    sub_xml_node.attr = {};

    -- 解析属性 <div xxx>
    xml_text = string_sub(xml_text, string_len(tagname) + 1);
    xml_text = xml.SkipSpaceCharacter(xml_text);
    -- 不等于 >
    while (string_sub(xml_text, 1, 1) ~= s_character_greater) do
        local attr_name = string_match(xml_text, "^[^%s=/>]+");
        if (attr_name == nil) then
            _G_error("invalid attr_name!!!")
        end
        xml_text = string_sub(xml_text, string_len(attr_name) + 1);
        xml_text = xml.SkipSpaceCharacter(xml_text);
        if (string_sub(xml_text, 1, 1) == s_character_equal) then
            xml_text = string_sub(xml_text, 2);
            local string_flag = string_sub(xml_text, 1, 1)
            if (string_flag == s_character_double_apostrophe or string_flag == s_character_apostrophe) then
                -- 带引号字符串
                xml_text = string_sub(xml_text, 2);
                local pos = string_find(xml_text, string_flag, 1, true);
                if (pos == nil) then
                    _G_error("invalid xml!!!")
                end
                local attr_value = string_sub(xml_text, 1, pos - 1);
                xml_text = string_sub(xml_text, pos + 1);
                sub_xml_node.attr[attr_name] = attr_value;
            else
                -- 不带引号字符串
                local attr_value = string_match(xml_text, "^[^%s/>]+");
                if (attr_value == nil) then
                    _G_error("invalid attr_value!!!")
                end
                xml_text = string_sub(xml_text, string_len(attr_value) + 1);
                sub_xml_node.attr[attr_name] = attr_value;
            end
        else
            -- 布尔值
            sub_xml_node.attr[attr_name] = true;
        end
    end

    xml_text = xml.SkipSpaceCharacter(xml_text);
    if (string_sub(xml_text, 1, 2) == "/>") then
        xml_node[#xml_node + 1] = sub_xml_node;
        return string_sub(xml_text, 3);
    end
    if (string_sub(xml_text, 1, 1) ~= s_character_greater) then
        _G_error("invalid xml!!!")
    end
    xml_text = string_sub(xml_text, 2);
    if (xml.IsLeafNode(tagname)) then
        -- 解析文本节点
        local pos = string_find(xml_text, "</" .. tagname .. ">", 1, true);
        if (pos == nil) then
            _G_error("invalid xml!!!")
        end
        sub_xml_node[#sub_xml_node + 1] = string_sub(xml_text, 1, pos - 1);
        xml_text = string_sub(xml_text, pos);
    else
        -- 解析子节点
        xml_text = xml.ParseXmlNodeAndText(xml_text, sub_xml_node);
    end
    xml_text = xml.SkipSpaceCharacter(xml_text);
    if (1 ~= string_find(xml_text, "</" .. tagname .. ">", 1, true)) then
        _G_error("invalid xml!!!")
    end
    xml_text = string_sub(xml_text, string_len(tagname) + 4);
    xml_node[#xml_node + 1] = sub_xml_node;
    return xml_text;
end

function xml.ParseXmlNodeAndText(xml_text, xml_node)
    local s_min_tag_len = 4; -- </p>  <a/>

    while (xml_text ~= "") do
        xml_text = xml.SkipSpaceCharacter(xml_text);
        xml_text = xml.ParseAnnotation(xml_text);
        xml_text = xml.SkipSpaceCharacter(xml_text);
        xml_text = xml.ParseText(xml_text, xml_node);
        xml_text = xml.SkipSpaceCharacter(xml_text);
        if (xml_text == "") then
            break
        end
        if (string_len(xml_text) < s_min_tag_len or string_sub(xml_text, 1, 1) ~= s_character_less) then
            _G_error("invalid xml!!!")
        end
        if (string_sub(xml_text, 2, 2) == s_character_slash) then
            break
        end -- </p>
        xml_text = xml.ParseAnnotation(xml_text);
        xml_text = xml.SkipSpaceCharacter(xml_text);
        xml_text = xml.ParseXmlNode(xml_text, xml_node);
        xml_text = xml.SkipSpaceCharacter(xml_text);
    end
    return xml_text;
end

function xml.FromString(xml_text)
    local xml_node = {};
    commonlib.xpcall(xml.ParseXmlNodeAndText, xml_text, xml_node);
    return xml_node;
end



function xml.Test()
    local str = [[
        <div style="width:100%;">
            <div>hello world</div>
        </div>
        ]]

    commonlib.print(xml.FromString(str));
end

return xml;
